﻿#include "OiPCH.hpp"
#include "OiLineView.hpp"
#include "OiDiffAxis.hpp"

#include <vector>
#include <string>
#include <stdint.h>

using namespace std;

#include <QApplication>
#include <QMainWindow>
#include <QStatusBar>
#include <QToolButton>
#include <QMap>
#include <QImage>
#include <QPainter>
#include <QPicture>
#include <QUuid>
#include <QAction>
#include <QToolBar>
#include <QToolTip>
#include <QListWidget>
#include <QTreeWidget>
#include <QWheelEvent>
#include <QGraphicsProxyWidget>
#include <QMenu>
#include <QTimer>
#include <QRectF>
#include <QMatrix>
#include <QKeyEvent>
#include <QBitmap>
#include <QVBoxLayout>
#include <QOpenGLWidget>

#include "Core/OiSettings.hpp"
#include "View/OiScene.hpp"
#include "Ctrl/OiAxis.hpp"

#

namespace Oi
{
    namespace PLScannerCore
    {
        class LineViewImpl;

        class lineItem : public QGraphicsItem
        {
        public:
            lineItem(QGraphicsItem *parent = nullptr) :QGraphicsItem(parent)
            { }

            QRectF boundingRect() const;

            void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = Q_NULLPTR);

        public:
            QList<QList<QPointF>> _profiles;
            LineViewImpl* _impl;
        };

        class LineViewImpl : public Impl<LineView>, public QObject
        {
        public:
            void init();
            void exit();

        public:
            bool preview(const QList<QList<QPointF>>& profiles);
            bool preview(const QList<QPointF>& profile);

        public:
            void relayout(int w = -1, int h = -1);
            void updateAxis();

        public:
            int _mode;

            lineItem* _line;

            DiffAxis* _hAxis;
           // QGraphicsProxyWidget* _hAxisProxy;

            DiffAxis* _vAxis;
         //   QGraphicsProxyWidget* _vAxisProxy;

            View::Scene* _scene;

            Qt::Orientation _orientation;

            QList<QList<QPointF>> _profiles;

            QList<QPointF> _profile;

            double _tol;

            double _eTol;
        };

        QRectF lineItem::boundingRect() const
        {
            QRectF rect;

            for(auto profile : _profiles)
            {
                auto pR = QPolygonF(profile.toVector()).boundingRect();
                if(rect.isEmpty())
                {
                    rect = pR;
                }
                else
                {
                    rect |= pR;
                }
            }

            return rect.normalized().adjusted(-0.1, -0.1, 0.1, 0.1);
        }

        void lineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget /*= Q_NULLPTR*/)
        {
            auto transform = painter->transform();

            double tol = _impl->_tol;

            painter->save();
            //painter->translate(QPointF(boundingRect().topLeft().x(), -boundingRect().topLeft().y()));
            painter->scale(1.0 / transform.m11(), 1.0 / transform.m22());

            QVector<QPointF> _okPoints;
            QVector<QPointF> _ngPoints;

            int bingSize = 10;

            QPointF lineStart;
            QPointF lineEnd;

            for(auto profile : _profiles)
            {
                /*
                for(int i = 0; i < profile.size() - 1; i++)
                {
                    auto p1 = profile[i];
                    auto p2 = profile[i + 1];

                    if(fabs(p1.y()) < tol && fabs(p2.y()) < tol)
                    {
                        _okPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                        _okPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                    }
                    else if(p1.y() >= tol && p2.y() < tol && p2.y() >= -tol)
                    {
                        QLineF l1(p1, p2);
                        QLineF l2(QPointF(1, tol), QPointF(0, tol));
                        QPointF p3;
                        if(PLScannerUtil::intersect(l1, l2, p3))
                        {
                            _ngPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                            _ngPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));

                            _okPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));
                            _okPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                        }
                    }
                    else if(p1.y() >= tol && p2.y() < -tol)
                    {
                        QLineF l1(p1, p2);
                        QLineF l2(QPointF(1, tol), QPointF(0, tol));
                        QLineF l3(QPointF(1, -tol), QPointF(0, -tol));
                        QPointF p3, p4;
                        if(PLScannerUtil::intersect(l1, l2, p3) && PLScannerUtil::intersect(l1, l3, p4))
                        {
                            _ngPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                            _ngPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));

                            _okPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));
                            _okPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));

                            _ngPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));
                            _ngPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                        }
                    }
                    else if(p1.y() < tol && p1.y() >= -tol && p2.y() < -tol)
                    {
                        QLineF l1(p1, p2);
                        QLineF l3(QPointF(1, -tol), QPointF(0, -tol));
                        QPointF p4;
                        if(PLScannerUtil::intersect(l1, l3, p4))
                        {
                            _okPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                            _okPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));

                            _ngPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));
                            _ngPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                        }
                    }
                    else if(p2.y() >= tol && p1.y() < tol && p1.y() >= -tol)
                    {
                        QLineF l1(p1, p2);
                        QLineF l2(QPointF(1, tol), QPointF(0, tol));
                        QPointF p3;
                        if(PLScannerUtil::intersect(l1, l2, p3))
                        {
                            _okPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                            _okPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));

                            _ngPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));
                            _ngPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                        }
                    }
                    else if(p2.y() >= tol && p1.y() < -tol)
                    {
                        QLineF l1(p1, p2);
                        QLineF l2(QPointF(1, tol), QPointF(0, tol));
                        QLineF l3(QPointF(1, -tol), QPointF(0, -tol));
                        QPointF p3, p4;
                        if(PLScannerUtil::intersect(l1, l2, p3) && PLScannerUtil::intersect(l1, l3, p4))
                        {
                            _ngPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                            _ngPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));

                            _okPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));
                            _okPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));

                            _ngPoints.append(QPointF(p3.x() * transform.m11(), p3.y() * transform.m22()));
                            _ngPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                        }
                    }
                    else if(p2.y() < tol && p2.y() >= -tol && p1.y() < -tol)
                    {
                        QLineF l1(p1, p2);
                        QLineF l3(QPointF(1, -tol), QPointF(0, -tol));
                        QPointF p4;
                        if(PLScannerUtil::intersect(l1, l3, p4))
                        {
                            _ngPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                            _ngPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));

                            _okPoints.append(QPointF(p4.x() * transform.m11(), p4.y() * transform.m22()));
                            _okPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                        }
                    }
                    else if(( p1.y() >= tol && p2.y() >= tol ) || ( p1.y() < -tol && p2.y() < -tol ))
                    {
                        _ngPoints.append(QPointF(p1.x() * transform.m11(), p1.y() * transform.m22()));
                        _ngPoints.append(QPointF(p2.x() * transform.m11(), p2.y() * transform.m22()));
                    }
                }
                */
                for (int i = 0; i < profile.size() / bingSize; i++)
                {
                    QPointF value = QPointF(0.0, 0.0);
                    for (int j = 0; j < bingSize; j++)
                    {
                        value += profile[i * bingSize + j];
                    }

                    value /= bingSize;

                    lineStart = lineEnd;
                    lineEnd = value;

                    if (!lineStart.isNull() && !lineEnd.isNull())
                    {
                        _okPoints.append(QPointF(lineStart.x() * transform.m11(), lineStart.y() * transform.m22()));
                        _okPoints.append(QPointF(lineEnd.x() * transform.m11(), lineEnd.y() * transform.m22()));
                    }
                }

            }

            painter->setPen(QPen(PLScannerStyle::ChartLineColor(), 3));
            painter->drawLines(_okPoints.data(), _okPoints.size() / 2);

            painter->restore();
        }

        void LineViewImpl::init()
        {
            OI_Q(LineView);

            _mode = 0;

            _tol = -1.0;

            _eTol = -1.0;

            q->setRenderHints(q->renderHints());
            q->setDragMode(QGraphicsView::RubberBandDrag);
            q->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            q->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
            q->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
            q->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
            q->setMouseTracking(true);
            q->setFrameShape(QFrame::NoFrame);
            q->setBackgroundBrush(PLScannerStyle::ChartBgColor());
            q->setAttribute(Qt::WA_MacNormalSize);

            _scene = new View::Scene(q);

            q->setScene(_scene);

            _vAxis = new DiffAxis(q);
            _vAxis->setMode(DiffAxis::vertical);
            _vAxis->setType(DiffAxis::rangeType);
            _vAxis->setAttribute(Qt::WA_GroupLeader);
//             _vAxisProxy = _scene->addWidget(_vAxis);
//             _vAxisProxy->setFlag(QGraphicsItem::ItemIsFocusable, false);
//             _vAxisProxy->setZValue(300);
            _vAxis->setStyleSheet("font-size:12pt;background-color:transparent");

            _hAxis = new DiffAxis(q);
            _hAxis->setMode(DiffAxis::horiztal);
            _hAxis->setType(DiffAxis::rangeType);
            _hAxis->setAttribute(Qt::WA_GroupLeader);
//             _hAxisProxy = _scene->addWidget(_hAxis);
//             _hAxisProxy->setFlag(QGraphicsItem::ItemIsFocusable, false);
//             _hAxisProxy->setZValue(300);
            _hAxis->setStyleSheet("font-size:12pt;background-color:transparent");

            _line = new lineItem();
            _line->_impl = this;

            _scene->addItem(_line);
            _line->setZValue(1);

            _scene->setSceneRect(1.0 * INT_MIN, 1.0 * INT_MIN, 2.0 * INT_MAX, 2.0 * INT_MAX);

            auto transform = q->transform();
            transform.rotate(180.0, Qt::XAxis);
            q->setTransform(transform);

            q->setCursor(Qt::ArrowCursor);
        }

        void LineViewImpl::exit()
        {
            if (_line)
            {
                delete _line;
            }

            if(_scene)
            {
                delete _scene;
            }
        }

        void LineViewImpl::relayout(int w, int h)
        {
            OI_Q(LineView);

            if(w == -1 || h == -1)
            {
                w = q->viewport()->width();
                h = q->viewport()->height();
            }

            _hAxis->setGeometry(0, h / 2 - 16, w, 32);
            _vAxis->setGeometry(w / 2 - 16, 0, 32, h);

            updateAxis();
        }

        bool LineViewImpl::preview(const QList<QList<QPointF>>& profiles)
        {
            _profiles = profiles;
            _line->_profiles = profiles;

            if(!_line->_profiles.isEmpty())
            {
                if(OI_SELF()->testAttribute(Qt::WA_MacNormalSize))
                {
                    OI_SELF()->zoomFit();
                }
            }

            return true;
        }

        bool LineViewImpl::preview(const QList<QPointF>& profile)
        {
            _profile = profile;

            _line->_profiles.clear();
            _line->_profiles.push_back(profile);

            if(!_line->_profiles.isEmpty())
            {
                OI_SELF()->zoomFit();
            }

            return true;
        }

        void LineViewImpl::updateAxis()
        {
            OI_Q(LineView);

            QPointF tl = q->mapToScene(0, 0);
            QPointF br = q->mapToScene(q->viewport()->width(), q->viewport()->height());

            double step = 1.0;

            if (br.y() >= tl.y())
            {
                step = 0.1 * qFloor(2.0 * (br.y() - tl.y()));
            }
            else
            {
                step = 0.1 * qCeil(2.0 * (br.y() - tl.y()));
            }
            
            if(step < 0.1 && step >= 0.0)
            {
                step = 0.1;
            }
            else if(step > -0.1 && step < 0.0)
            {
                step = -0.1;
            }

            _vAxis->setStep(step);
            _vAxis->setStart(tl.y());
            _vAxis->setEnd(br.y());
            _vAxis->setDecimals(2);
            _vAxis->update();

            step = qFloor(fabs(tl.x() - br.x()) / 10);
            if(step < 0.1) step = 0.1;
            _hAxis->setStep(step);
            _hAxis->setStart(tl.x());
            _hAxis->setEnd(br.x());
            _hAxis->setDecimals(0);
            _hAxis->update();

            emit q->axisHasChanged();
        }

        LineView::LineView(Qt::Orientation orientation/* = Qt::Horizontal*/, QWidget* parent) : QGraphicsView(parent)
        {
            OI_I(LineView)->init();
            d->_orientation = orientation;
        }

        LineView::~LineView()
        {
            OI_E(LineView)->exit();
        }

        QList<QPointF> LineView::profile() const
        {
            return OI_IMPL()->_profile;
        }

        QList<QList<QPointF>> LineView::profiles() const
        {
            return OI_IMPL()->_profiles;
        }

        void LineView::display(const QList<QList<QPointF>>& profiles)
        {
            OI_F(LineView);
            d->preview(profiles);
        }

        void LineView::display(const QList<QPointF>& profile)
        {
            OI_F(LineView);
            d->preview(profile);
        }

        void LineView::setTol(double tol, double eTol)
        {
            OI_F(LineView);

            d->_tol = tol;
            d->_eTol = eTol;
        }

        void LineView::invalidate()
        {
            if(scene())
            {
                scene()->invalidate();
            }
        }

        void LineView::zoomOut(double factor)
        {
            OI_F(LineView);

            qreal factorX = transform().m11();
            qreal factorY = transform().m22();
            scale(1.0 / factorX, 1.0 / factorY);

            d->relayout();
        }

        void LineView::zoomFit()
        {
            OI_F(LineView);

            fitInView(d->_line, Qt::IgnoreAspectRatio);
            d->relayout();
            update();
        }

        void LineView::mousePressEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            if(event->button() == Qt::MiddleButton || ( event->button() == Qt::LeftButton && ( event->modifiers() & Qt::CTRL ) ))
            {
                setDragMode(QGraphicsView::ScrollHandDrag);
                QMouseEvent evt(event->type(), event->pos(), Qt::LeftButton, event->buttons(), event->modifiers());
                QGraphicsView::mousePressEvent(&evt);
                event->accept();
                d->relayout();
                return;
            }
            QGraphicsView::mousePressEvent(event);
        }

        void LineView::mouseReleaseEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            if(event->button() == Qt::MiddleButton || ( event->button() == Qt::LeftButton && ( event->modifiers() & Qt::CTRL ) ))
            {
                QMouseEvent evt(event->type(), event->pos(), Qt::LeftButton, event->buttons(), event->modifiers());
                QGraphicsView::mouseReleaseEvent(&evt);
                event->accept();
                setDragMode(QGraphicsView::RubberBandDrag);
                d->relayout();
                return;
            }

            QGraphicsView::mouseReleaseEvent(event);
        }

        void LineView::mouseMoveEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            QGraphicsView::mouseMoveEvent(event);

            if(dragMode() == QGraphicsView::ScrollHandDrag)
            {
                d->relayout();
            }
        }

        void LineView::mouseDoubleClickEvent(QMouseEvent *event)
        {
            OI_F(LineView);

            QGraphicsView::mouseDoubleClickEvent(event);

            if(event->button() == Qt::MiddleButton)
            {
                zoomFit();
            }

        }

        void LineView::wheelEvent(QWheelEvent *event)
        {
            OI_F(LineView);
          
            if(event->delta() > 0)
            {
                if(transform().m11() < 1e+5)
                {
                    scale(1.0 * 1.15, 1.0 * 1.15);
                    d->relayout();
                }
            }
            else
            {
                if(transform().m11() > 1e-5)
                {
                    scale(1.0 / 1.15, 1.0 / 1.15);
                    d->relayout();
                }
            }
        }

        void LineView::resizeEvent(QResizeEvent *event)
        {
            OI_F(LineView);

            QGraphicsView::resizeEvent(event);

            d->relayout();

            zoomFit();
        }

        void LineView::showEvent(QShowEvent *event)
        {
            OI_F(LineView);
            QGraphicsView::showEvent(event);
            d->relayout();
        }

        void LineView::hideEvent(QHideEvent *event)
        {
            OI_F(LineView);
            QGraphicsView::hideEvent(event);
            d->relayout();
        }


        void LineView::drawBackground(QPainter *painter, const QRectF &rect)
        {
            OI_F(LineView);

            if (d->_tol > 0)
            {
                auto rt = qMax(rect.top(), rect.bottom());
                auto rb = qMin(rect.top(), rect.bottom());

                auto transform = painter->transform();

                painter->fillRect(QRectF(QPointF(rect.left(), rt), QPointF(rect.right(), d->_tol)), QColor("#fdd8d8"));

                painter->fillRect(QRectF(QPointF(rect.left(), -d->_tol), QPointF(rect.right(), rb)), QColor("#fdd8d8"));

                painter->save();

                painter->scale(1.0 / transform.m11(), 1.0 / transform.m22());

                painter->setPen(QPen(Qt::red, 2.0, Qt::DotLine));

                painter->drawLine(QPointF(rect.left() * transform.m11(), d->_tol * transform.m22()), QPointF(rect.right() * transform.m11(), d->_tol * transform.m22()));

                painter->drawLine(QPointF(rect.left()  * transform.m11(), -d->_tol * transform.m22()), QPointF(rect.right() * transform.m11(), -d->_tol * transform.m22()));

                painter->restore();

                if(d->_eTol > 0)
                {
                    painter->fillRect(QRectF(QPointF(rect.left(), d->_tol), QPointF(rect.right(), d->_eTol)), QColor("#faf8d4"));

                    painter->fillRect(QRectF(QPointF(rect.left(), -d->_eTol), QPointF(rect.right(), -d->_tol)), QColor("#faf8d4"));
                }
            }
        }

        void LineView::paintEvent(QPaintEvent *event)
        {
            QGraphicsView::paintEvent(event);
        }
    }
}